﻿// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using CIP4.Common;
using CIP4.Item;
using CIP4.Simple;

namespace CIP4.DataProvider.MultiThread
{
    public class DataProvider : CIP4.Simple.DataProvider, IDataProvider
    {
        #region IDataProvider Members

        public string ConnectionString { get; private set; }

        IDataProvider Cache { get; set; }

        IDataProvider Target { get; set; }

        private ProviderTypesEnum TargetType;

        private int NumberOfThreads = CIP4MultiThreadConnectionAttribute.DEFAULTNUMBEROFTHREADS;

        public DataProvider(CIP4ConnectionAttribute DataProviderConnection, IDataProvider cachingProvider, CIP4.Common.LoggerDelegate logger)
            : base(DataProviderConnection)
        {
            if (!(DataProviderConnection is CIP4MultiThreadConnectionAttribute))
                LoggerWrapper(CIP4.Common.MajorEnum.Error, MinorEnum.UnexpectedConnectionType,
                    "{0} is not an acceptable connection type", new object[] { DataProviderConnection.ToString() }, logger);
            else
            {
                LoggerWrapper(CIP4.Common.MajorEnum.Trace, MinorEnum.InitializingProvider,
                    "Initalizing Provider: {0}", new object[] { DataProviderConnection }, logger);
                CIP4MultiThreadConnectionAttribute con = (CIP4MultiThreadConnectionAttribute)DataProviderConnection;

                if (con.Target == null)
                    LoggerWrapper(MajorEnum.Error, MinorEnum.InitializingProvider,
                        "A MultiThread connection attribute must have a Target", new object[] { }, logger);
                else
                {
                    NumberOfThreads = con.NumberOfThreads;
                    TargetType = con.Target.ProviderKind;
                    Target = GetProvider(con.Target, null, logger);
                }

                ConnectionString = DataProviderConnection.ToString();

                Cache = cachingProvider;
            }
        }

        public void DeleteCollection(CollectionType collection, CIP4.Common.LoggerDelegate logger)
        {
            if (!LoggerWrapper(CIP4.Common.MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.DeleteCollection }, logger))
                return;

            try
            {
                Target.DeleteCollection(collection, logger);
            }
            catch (Exception ex)
            {
                LoggerWrapper(CIP4.Common.MajorEnum.Error, MinorEnum.ErrorDeletingCollection,
                    "Error deleting collection: {0}", new object[] { ex }, logger);
            }
            // Information messages are the responsibility of the target provider
        }

        public override IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor)
        {
            IList<object> items = new List<object>();
            if (!LoggerWrapper(CIP4.Common.MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.GetItems }, cursor.Logger))
                return items;

            if (Cache != null)
                Cache.SaveItems(items, collection, cursor.GetSerializer);

            return Target.GetItems(collection, selectionExpression, cursor);
            // Information messages are the responsibility of the target provider
        }

        public override void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            ISerializerType serializer = null;

            int deletedCount = 0;

            LoggerDelegate originalLogger = null;

            List<List<ExternalIDType>> threadIds = new List<List<ExternalIDType>>();

            List<Thread> itemIds = new List<Thread>();

            int itemOrdinal = 0;

            try
            {

                LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
                {
                    if (status == MajorEnum.Information && minorStatus == MinorEnum.PurgeItems)
                    {
                        lock (threadIds)
                        {
                            deletedCount += (int)paras[0];
                        }
                        return LoggerResponseEnum.keepGoing;
                    }
                    else
                    {
                        // Why? Don't ask yet - my head hurts
                        return originalLogger(status, minorStatus, message, paras);
                    }
                };

                GetSerializerDelegate localGetSerializer = delegate(SerializerKindEnum serializerKind)
                {
                    lock (threadIds)
                    {
                        if (serializer == null)
                        {
                            // OK, you can ask now - see "Why? Don't ask yet"...
                            // if localLogger used serializer.Logger it would stackoverflow
                            serializer = getSerializer(serializerKind);
                            originalLogger = serializer.Logger;
                            serializer.PushLogger(localLogger);
                        }
                    }
                    return serializer;
                };

                foreach (ExternalIDType item in externalIDs)
                {
                    if (threadIds.Count <= itemOrdinal % NumberOfThreads)
                        threadIds.Add(new List<ExternalIDType>());
                    threadIds[itemOrdinal % NumberOfThreads].Add(item);
                    itemOrdinal++;
                }

                foreach (List<ExternalIDType> threadIdsList in threadIds)
                {
                    ParameterizedThreadStart pts = new ParameterizedThreadStart(ItemPurger);
                    Thread itemThread = new Thread(pts);
                    itemThread.Start(
                        new ItemPurgerParametersType
                        {
                            Ids = threadIdsList,
                            Collection = collection,
                            GetSerializer = localGetSerializer
                        });
                    itemIds.Add(itemThread);
                }

                for (int index = 0; index < itemIds.Count; index++)
                    if (itemIds[index].IsAlive)
                        itemIds[index].Join();

                if (Cache != null)
                    Cache.PurgeItems(externalIDs, collection, getSerializer);

            }
            finally
            {
                if (serializer != null)
                {
                    serializer.PopLogger();
                    LoggerWrapper(MajorEnum.Information, MinorEnum.PurgeItems,
                        "{0} items deleted from {1}", new object[] { deletedCount, collection.Name }, serializer.Logger);
                }
            }
        }

        public void SaveItems(IEnumerable<object> items, CollectionType collection, GetSerializerDelegate getSerializer)
        {

            ISerializerType serializer = null;

            int savedCount = 0;

            LoggerDelegate originalLogger = null;

            List<List<object>> threadItems = new List<List<object>>();

            List<Thread> itemThreads = new List<Thread>();

            int itemOrdinal = 0;

            try
            {
                LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
                {
                    if (status == MajorEnum.Information && minorStatus == MinorEnum.SaveItems)
                    {
                        lock (threadItems)
                        {
                            savedCount += (int)paras[0];
                        }
                        return LoggerResponseEnum.keepGoing;
                    }
                    else
                    {
                        return originalLogger(status, minorStatus, message, paras);
                    }
                };

                GetSerializerDelegate localGetSerializer = delegate(SerializerKindEnum serializerKind)
                {
                    lock (threadItems)
                    {
                        if (serializer == null)
                        {
                            // see "Why? Don't ask yet"...
                            serializer = getSerializer(serializerKind);
                            originalLogger = serializer.Logger;
                            serializer.PushLogger(localLogger);
                        }
                    }
                    return serializer;
                };

                foreach (object item in items)
                {
                    if (threadItems.Count <= itemOrdinal % NumberOfThreads)
                        threadItems.Add(new List<object>());
                    threadItems[itemOrdinal % NumberOfThreads].Add(item);
                    itemOrdinal++;
                }

                foreach (List<object> threadItemsList in threadItems)
                {
                    ParameterizedThreadStart pts = new ParameterizedThreadStart(ItemSaver);
                    Thread itemThread = new Thread(pts);
                    itemThread.Start(
                        new ItemSaverParametersType
                        {
                            Items = threadItemsList,
                            Collection = collection,
                            GetSerializer = localGetSerializer
                        });
                    itemThreads.Add(itemThread);
                }

                for (int index = 0; index < itemThreads.Count; index++)
                    if (itemThreads[index].IsAlive)
                        itemThreads[index].Join();

                if (Cache != null)
                    Cache.SaveItems(items, collection, getSerializer);

            }
            finally
            {
                if (serializer != null)
                {
                    serializer.PopLogger();
                    LoggerWrapper(MajorEnum.Information, MinorEnum.SaveItems,
                        "{0} items saved in {1}", new object[] { savedCount, collection.Name }, serializer.Logger);
                }
            }
        }

        public void Terminate(CIP4.Common.LoggerDelegate logger)
        {
            Target.Terminate(logger);
        }

        #endregion

        #region Helper Functions

        class ItemSaverParametersType
        {
            public IEnumerable<object> Items;
            public CollectionType Collection;
            public GetSerializerDelegate GetSerializer;
        }

        private void ItemSaver(object paramtersObject)
        {
            ItemSaverParametersType paramters = (ItemSaverParametersType)paramtersObject;
            Target.SaveItems(paramters.Items, paramters.Collection, paramters.GetSerializer);
        }

        class ItemPurgerParametersType
        {
            public IEnumerable<ExternalIDType> Ids;
            public CollectionType Collection;
            public GetSerializerDelegate GetSerializer;
        }

        private void ItemPurger(object paramtersObject)
        {
            ItemPurgerParametersType paramters = (ItemPurgerParametersType)paramtersObject;
            Target.PurgeItems(paramters.Ids, paramters.Collection, paramters.GetSerializer);
        }

        #endregion
    }
}
